﻿using SharpDepend.Classes;
using SharpDepend.XamarinForms.Controls;
using System;
using System.Collections.Generic;
using System.Linq;

namespace SharpDepend.XamarinForms
{
    class NativeGraphicsDisplay : NativeView, IGraphicsDisplay
    {
        private static int sID;

        private IGraphicsDisplay mNativeGraphicsDisplay;
        private double mWidth;
        private double mHeight;

        private int ID;

        internal NativeGraphicsDisplay(IGraphicsDisplay nativeGraphicsDisplay)
        {
            ID = sID++;
            
            mNativeGraphicsDisplay = nativeGraphicsDisplay;
            NativeControl = nativeGraphicsDisplay;

            OnNativeViewChanged += NativeGraphicsDisplay_OnNativeViewChanged;
            OnNativeSizeChanged += NativeGraphicsDisplay_OnNativeSizeChanged;
        }

        void NativeGraphicsDisplay_OnNativeViewChanged()
        {
            GraphicsDevice.SetViewportSize(mWidth, mHeight);
        }

        void NativeGraphicsDisplay_OnNativeSizeChanged(double width, double height)
        {
            mWidth = width;
            mHeight = height;
            if (GraphicsDevice != null)
            {
                GraphicsDevice.SetViewportSize(width, height);
            }
        }

        public event Action DeviceLoaded
        {
            add
            {
                mNativeGraphicsDisplay.DeviceLoaded += value;
            }
            remove
            {
                mNativeGraphicsDisplay.DeviceLoaded -= value;
            }
        }

        public event Action DeviceUnloaded
        {
            add
            {
                mNativeGraphicsDisplay.DeviceUnloaded += value;
            }
            remove
            {
                mNativeGraphicsDisplay.DeviceUnloaded -= value;
            }
        }

        public void GraphicsDeviceChanged(IGraphicsDevice device)
        {
            mNativeGraphicsDisplay.GraphicsDeviceChanged(device);

            if (device != null)
            {
                device.SetViewportSize(mWidth, mHeight);
            }
        }

        public IGraphicsDevice GraphicsDevice
        {
            get { return mNativeGraphicsDisplay.GraphicsDevice; }
        }

        public event Classes.EventHandlerMouseDownAction PointDown
        {
            add
            {
                mNativeGraphicsDisplay.PointDown += value;
            }
            remove
            {
                mNativeGraphicsDisplay.PointDown -= value;
            }
        }

        public event Classes.EventHandlerMouseAction PointMove
        {
            add
            {
                mNativeGraphicsDisplay.PointMove += value;
            }
            remove
            {
                mNativeGraphicsDisplay.PointMove -= value;
            }
        }

        public event Classes.EventHandlerMouseAction PointUp
        {
            add
            {
                mNativeGraphicsDisplay.PointUp += value;
            }
            remove
            {
                mNativeGraphicsDisplay.PointUp -= value;
            }
        }

        public event Classes.EventHandlerMouseWheelAction PointWheel
        {
            add
            {
                mNativeGraphicsDisplay.PointWheel += value;
            }
            remove
            {
                mNativeGraphicsDisplay.PointWheel -= value;
            }
        }

        public event EventHandlerMouseAction PointHover
        {
            add
            {
                mNativeGraphicsDisplay.PointHover += value;
            }
            remove
            {
                mNativeGraphicsDisplay.PointHover -= value;
            }
        }

        public bool OnMouseDown(ref Datatypes.TouchLocation location, int id, int touchCount)
        {
            return mNativeGraphicsDisplay.OnMouseDown(ref location, id, touchCount);
        }

        public void OnMouseMove(ref Datatypes.TouchLocation location, int id, int touchCount)
        {
            mNativeGraphicsDisplay.OnMouseMove(ref location, id, touchCount);
        }

        public void OnMouseUp(ref Datatypes.TouchLocation location, int id, int touchCount)
        {
            mNativeGraphicsDisplay.OnMouseUp(ref location, id, touchCount);
        }

        public void OnMouseWheel(ref Datatypes.TouchLocation location)
        {
            mNativeGraphicsDisplay.OnMouseWheel(ref location);
        }

        public void OnMouseHover(ref Datatypes.TouchLocation location, int id, int touchCount)
        {
            mNativeGraphicsDisplay.OnMouseHover(ref location, id, touchCount);
        }
        
        public object Locker
        {
            get { return mNativeGraphicsDisplay.Locker; }
        }

        public bool Ready
        {
            get { return mNativeGraphicsDisplay.Ready; }
        }

        public bool Enable
        {
            get
            {
                return mNativeGraphicsDisplay.Enable;
            }

            set
            {
                mNativeGraphicsDisplay.Enable = value;
            }
        }

        public bool RenderTextures
        {
            get
            {
                return mNativeGraphicsDisplay.RenderTextures;
            }

            set
            {
                mNativeGraphicsDisplay.RenderTextures = value;
            }
        }

        public IGraphicsDevice ManualGraphicsDevice
        {
            get
            {
                return mNativeGraphicsDisplay.ManualGraphicsDevice;
            }

            set
            {
                mNativeGraphicsDisplay.ManualGraphicsDevice = value;
            }
        }

        public void RegisterTexture(Datatypes.Texture texture)
        {
            mNativeGraphicsDisplay.RegisterTexture(texture);
        }

        public void UnregisterTexture(Datatypes.Texture texture)
        {
            mNativeGraphicsDisplay.UnregisterTexture(texture);
        }

        public bool ContainsTexture(Datatypes.Texture texture)
        {
            return mNativeGraphicsDisplay.ContainsTexture(texture);
        }
    }

    public class GraphicsDisplayManager : IGraphicsDisplayManager
    {
        private IGraphicsDisplayManager mGraphicsDisplayManager;
        private List<GraphicDevice> mXamarinFormsGraphicsDevices;
        private List<GraphicDevice> mDevices;
        private List<IGraphicsDisplay> mUsedDisplays;
        private GraphicDevice mCurrentUsingDevice;

        public GraphicDevice CurrentUsingDevice
        {
            get
            {
                if (mCurrentUsingDevice != null)
                {
                    return mCurrentUsingDevice;
                }

                return mGraphicsDisplayManager.CurrentUsingDevice;
            }
        }

        public GraphicsDisplayManager(IGraphicsDisplayManager graphicsDisplayManager)
        {
            mXamarinFormsGraphicsDevices = new List<GraphicDevice>();
            mUsedDisplays = new List<IGraphicsDisplay>();
            mDevices = new List<GraphicDevice>();
            mGraphicsDisplayManager = graphicsDisplayManager;
        }

        public IGraphicsDisplay CreateGraphicsDisplay()
        {
            IGraphicsDisplay nativeDisplay = mGraphicsDisplayManager.CreateGraphicsDisplay();

            NativeGraphicsDisplay display = new NativeGraphicsDisplay(nativeDisplay);

            SetupDisplay(display);

            return display;
        }
        
        public void SetGraphicsDevice(GraphicDevice device)
        {
            if (device == null)
            {
                throw new NullReferenceException("device");
            }
            if (!AvailableDevices.Contains(device))
            {
                throw new Exception("Device " + device + " must be added before to be used.");
            }
            
            if (mXamarinFormsGraphicsDevices.Contains(device))
            {
                mCurrentUsingDevice = device;

                foreach (var display in GraphicsDisplaysInUse)
                {
                    display.GraphicsDeviceChanged(CreateDevice(mCurrentUsingDevice));
                }
            }
            else
            {
                mGraphicsDisplayManager.SetGraphicsDevice(device);
            }
        }

        /// <summary>
        /// Add graphics device as a xamarin forms element.
        /// </summary>
        /// <param name="device">Device.</param>
        public void AddDevice(GraphicDevice device)
        {
            AddDevice(device, true);
        }

        /// <summary>
        /// Add graphics device.
        /// </summary>
        /// <param name="device">Device.</param>
        /// <param name="isXamarinFormsElement">True if device element is an xamarin forms.</param>
        public void AddDevice(GraphicDevice device, bool isXamarinFormsElement)
        {
            if (isXamarinFormsElement)
            {
                mXamarinFormsGraphicsDevices.Add(device);
            }
            else
            {
                mGraphicsDisplayManager.AddDevice(device);
            }
        }

        public IEnumerable<GraphicDevice> AvailableDevices
        {
            get
            {
                mDevices.Clear();
                mDevices.AddRange(mGraphicsDisplayManager.AvailableDevices);
                mDevices.AddRange(mXamarinFormsGraphicsDevices);
                return mDevices;
            }
        }

        public void SetupDisplay(IGraphicsDisplay display)
        {
            mUsedDisplays.Add(display);

            // Note: This is already done in CreateGraphicsDisplay();
            //mGraphicsDisplayManager.SetupDisplay(display);
        }

        public void AddMultitouch(IGraphicsDisplay display)
        {
            mGraphicsDisplayManager.AddMultitouch(display);
        }

        public IGraphicsDevice CreateDevice(GraphicDevice device)
        {
            return mGraphicsDisplayManager.CreateDevice(device);
        }

        public IEnumerable<IGraphicsDisplay> GraphicsDisplaysInUse
        {
            get
            {
                List<IGraphicsDisplay> inUse = new List<IGraphicsDisplay>();
                //inUse.AddRange(mGraphicsDisplayManager.GraphicsDisplaysInUse);
                inUse.AddRange(mUsedDisplays);
                return inUse;
            }
        }
    }
}
